home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
pluginy Firefox
/
1843
/
1843.xpi
/
content
/
firebug
/
dom.js
< prev
next >
Wrap
Text File
|
2010-01-15
|
62KB
|
2,066 lines
/* See license.txt for terms of usage */
FBL.ns(function() { with (FBL) {
// ************************************************************************************************
// Constants
const Cc = Components.classes;
const Ci = Components.interfaces;
const jsdIStackFrame = Ci.jsdIStackFrame;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const insertSliceSize = 18;
const insertInterval = 40;
const rxIdentifier = /^[$_A-Za-z][$_A-Za-z0-9]*$/
const ignoreVars =
{
"__firebug__": 1,
"eval": 1,
// We are forced to ignore Java-related variables, because
// trying to access them causes browser freeze
"java": 1,
"sun": 1,
"Packages": 1,
"JavaArray": 1,
"JavaMember": 1,
"JavaObject": 1,
"JavaClass": 1,
"JavaPackage": 1,
// internal firebug things
"_firebug": 1,
"_FirebugConsole": 1,
"_FirebugCommandLine": 1,
"loadFirebugConsole": 1,
"_getFirebugConsoleElement": 1,
};
// ************************************************************************************************
Firebug.DOMModule = extend(Firebug.Module,
{
initialize: function(prefDomain, prefNames)
{
Firebug.Module.initialize.apply(this, arguments);
Firebug.Debugger.addListener(this.DebuggerListener);
},
initContext: function(context, persistedState)
{
Firebug.Module.initContext.apply(this, arguments);
context.dom = {breakpoints: new DOMBreakpointGroup()};
},
loadedContext: function(context, persistedState)
{
context.dom.breakpoints.load(context);
},
destroyContext: function(context, persistedState)
{
Firebug.Module.destroyContext.apply(this, arguments);
context.dom.breakpoints.store(context);
},
shutdown: function()
{
Firebug.Module.shutdown.apply(this, arguments);
Firebug.Debugger.removeListener(this.DebuggerListener);
},
});
// ************************************************************************************************
const WatchRowTag =
TR({"class": "watchNewRow", level: 0},
TD({"class": "watchEditCell", colspan: 3},
DIV({"class": "watchEditBox a11yFocusNoTab", role: "button", 'tabindex' : '0',
'aria-label' : $STR('a11y.labels.press enter to add new watch expression')},
$STR("NewWatch")
)
)
);
const SizerRow =
TR({role : 'presentation'},
TD(),
TD({width: "30%"}),
TD({width: "70%"})
);
const DirTablePlate = domplate(Firebug.Rep,
{
memberRowTag:
TR({"class": "memberRow $member.open $member.type\\Row", _domObject: "$member",
$hasChildren: "$member.hasChildren",
role: "presentation",
level: "$member.level",
breakable: "$member.breakable",
breakpoint: "$member.breakpoint",
disabledBreakpoint: "$member.disabledBreakpoint"},
TD({"class": "memberHeaderCell"},
DIV({"class": "sourceLine memberRowHeader", onclick: "$onClickRowHeader"},
" "
)
),
TD({"class": "memberLabelCell", style: "padding-left: $member.indent\\px",
role: 'presentation'},
DIV({"class": "memberLabel $member.type\\Label"},
SPAN({"class": "memberLabelPrefix"}, "$member.prefix"),
SPAN("$member.name")
)
),
TD({"class": "memberValueCell", role : 'presentation'},
TAG("$member.tag", {object: "$member.value"})
)
),
tag:
TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0, onclick: "$onClick",
role: "tree", 'aria-label': $STR('aria.labels.dom properties')},
TBODY({role: 'presentation'},
SizerRow,
FOR("member", "$object|memberIterator",
TAG("$memberRowTag", {member: "$member"})
)
)
),
watchTag:
TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0,
_toggles: "$toggles", _domPanel: "$domPanel", onclick: "$onClick", role : 'tree'},
TBODY({role : 'presentation'},
SizerRow,
WatchRowTag
)
),
tableTag:
TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0,
_toggles: "$toggles", _domPanel: "$domPanel", onclick: "$onClick",
role: 'tree', 'aria-label': 'DOM properties'},
TBODY({role : 'presentation'},
SizerRow
)
),
rowTag:
FOR("member", "$members",
TAG("$memberRowTag", {member: "$member"})
),
memberIterator: function(object, level)
{
return Firebug.DOMBasePanel.prototype.getMembers(object, level, this.context);
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
onClick: function(event)
{
if (!isLeftClick(event))
return;
var row = getAncestorByClass(event.target, "memberRow");
var label = getAncestorByClass(event.target, "memberLabel");
var valueCell = row.getElementsByClassName("memberValueCell").item(0);
var object = Firebug.getRepObject(event.target);
var target = row.lastChild.firstChild;
var isString = hasClass(target,"objectBox-string");
var inValueCell = event.target == valueCell || event.target == target;
if (label && hasClass(row, "hasChildren") && !(isString && inValueCell))
{
var row = label.parentNode.parentNode;
this.toggleRow(row);
}
else
{
if (typeof(object) == "function")
{
Firebug.chrome.select(object, "script");
cancelEvent(event);
}
else if (event.detail == 2 && !object)
{
var panel = row.parentNode.parentNode.domPanel;
if (panel)
{
var rowValue = panel.getRowPropertyValue(row);
if (typeof(rowValue) == "boolean")
panel.setPropertyValue(row, !rowValue);
else
panel.editProperty(row);
cancelEvent(event);
}
}
}
},
toggleRow: function(row)
{
var level = parseInt(row.getAttribute("level"));
var toggles = row.parentNode.parentNode.toggles;
var panel = row.parentNode.parentNode.domPanel;
var target = row.lastChild.firstChild;
var isString = hasClass(target,"objectBox-string");
if (hasClass(row, "opened"))
{
removeClass(row, "opened");
if (isString)
{
var rowValue = panel.getRowPropertyValue(row);
row.lastChild.firstChild.textContent = '"' + cropMultipleLines(rowValue) + '"';
}
else
{
if (toggles)
{
var path = getPath(row);
// Remove the path from the toggle tree
for (var i = 0; i < path.length; ++i)
{
if (i == path.length-1)
delete toggles[path[i]];
else
toggles = toggles[path[i]];
}
}
var rowTag = this.rowTag;
var tbody = row.parentNode;
setTimeout(function()
{
for (var firstRow = row.nextSibling; firstRow; firstRow = row.nextSibling)
{
if (parseInt(firstRow.getAttribute("level")) <= level)
break;
tbody.removeChild(firstRow);
}
}, row.insertTimeout ? row.insertTimeout : 0);
}
}
else
{
setClass(row, "opened");
if (isString)
{
var rowValue = panel.getRowPropertyValue(row);
row.lastChild.firstChild.textContent = '"' + rowValue + '"';
}
else
{
if (toggles)
{
var path = getPath(row);
// Mark the path in the toggle tree
for (var i = 0; i < path.length; ++i)
{
var name = path[i];
if (toggles.hasOwnProperty(name))
toggles = toggles[name];
else
toggles = toggles[name] = {};
}
}
var context = panel ? panel.context : null;
var members = Firebug.DOMBasePanel.prototype.getMembers(target.repObject, level+1, context);
var rowTag = this.rowTag;
var lastRow = row;
var delay = 0;
var setSize = members.length;
var rowCount = 1;
while (members.length)
{
setTimeout(function(slice, isLast)
{
if (lastRow.parentNode)
{
var result = rowTag.insertRows({members: slice}, lastRow);
lastRow = result[1];
dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [null, result, rowCount, setSize]);
rowCount += insertSliceSize;
}
if (isLast)
delete row.insertTimeout;
}, delay, members.splice(0, insertSliceSize), !members.length);
delay += insertInterval;
}
row.insertTimeout = delay;
}
}
},
onClickRowHeader: function(event)
{
cancelEvent(event);
var rowHeader = event.target;
if (!hasClass(rowHeader, "memberRowHeader"))
return;
var row = getAncestorByClass(event.target, "memberRow");
if (!row)
return;
var panel = row.parentNode.parentNode.domPanel;
if (panel)
panel.breakOnProperty(row);
}
});
const ToolboxPlate = domplate(
{
tag:
DIV({"class": "watchToolbox", _domPanel: "$domPanel", onclick: "$onClick"},
IMG({"class": "watchDeleteButton closeButton", src: "blank.gif"})
),
onClick: function(event)
{
var toolbox = event.currentTarget;
toolbox.domPanel.deleteWatch(toolbox.watchRow);
}
});
// ************************************************************************************************
Firebug.DOMBasePanel = function() {}
Firebug.DOMBasePanel.prototype = extend(Firebug.ActivablePanel,
{
tag: DirTablePlate.tableTag,
getRealObject: function(object)
{
return unwrapObject(object);
},
rebuild: function(update, scrollTop)
{
dispatch([Firebug.A11yModel], 'onBeforeDomUpdateSelection', [this]);
var members = this.getMembers(this.selection, 0, this.context);
this.expandMembers(members, this.toggles, 0, 0, this.context);
this.showMembers(members, update, scrollTop);
},
/*
* @param object a user-level object wrapped in security blanket
* @param level for a.b.c, level is 2
* @param context
*/
getMembers: function(object, level, context)
{
if (!level)
level = 0;
var ordinals = [], userProps = [], userClasses = [], userFuncs = [],
domProps = [], domFuncs = [], domConstants = [];
try
{
// Special case for "arguments", which is not enumerable by for...in statement.
if (isArguments(object))
object = cloneArray(object);
var domMembers = getDOMMembers(object);
var insecureObject = unwrapObject(object);
for (var name in insecureObject) // enumeration is safe
{
// Ignore only global variables (properties of the |window| object).
// javascript.options.strict says ignoreVars is undefined.
if (ignoreVars[name] == 1 && (object instanceof Window))
{
continue;
}
var val;
try
{
val = insecureObject[name]; // getter is safe
}
catch (exc)
{
// Sometimes we get exceptions trying to access certain members
}
var ordinal = parseInt(name);
if (ordinal || ordinal == 0)
{
addMember(object, "ordinal", ordinals, name, val, level, 0, context);
}
else if (typeof(val) == "function")
{
if (isClassFunction(val))
addMember(object, "userClass", userClasses, name, val, level, 0, context);
else if (name in domMembers)
addMember(object, "domFunction", domFuncs, name, val, level, domMembers[name], context);
else
addMember(object, "userFunction", userFuncs, name, val, level, 0, context);
}
else
{
if (name in domMembers)
addMember(object, "dom", domProps, name, val, level, domMembers[name], context);
else if (name in domConstantMap)
addMember(object, "dom", domConstants, name, val, level, 0, context);
else
addMember(object, "user", userProps, name, val, level, 0, context);
}
}
}
catch (exc)
{
// Sometimes we get exceptions just from trying to iterate the members
// of certain objects, like StorageList, but don't let that gum up the works
//throw exc;
}
function sortName(a, b) { return a.name > b.name ? 1 : -1; }
function sortOrder(a, b) { return a.order > b.order ? 1 : -1; }
var members = [];
members.push.apply(members, ordinals);
if (Firebug.showUserProps)
{
userProps.sort(sortName);
members.push.apply(members, userProps);
}
if (Firebug.showUserFuncs)
{
userClasses.sort(sortName);
members.push.apply(members, userClasses);
userFuncs.sort(sortName);
members.push.apply(members, userFuncs);
}
if (Firebug.showDOMProps)
{
domProps.sort(sortName);
members.push.apply(members, domProps);
}
if (Firebug.showDOMFuncs)
{
domFuncs.sort(sortName);
members.push.apply(members, domFuncs);
}
if (Firebug.showDOMConstants)
members.push.apply(members, domConstants);
return members;
},
expandMembers: function (members, toggles, offset, level, context) // recursion starts with offset=0, level=0
{
var expanded = 0;
for (var i = offset; i < members.length; ++i)
{
var member = members[i];
if (member.level > level)
break;
if ( toggles.hasOwnProperty(member.name) )
{
member.open = "opened"; // member.level <= level && member.name in toggles.
if (member.type == 'string')
continue;
var newMembers = this.getMembers(member.value, level+1, context); // sets newMembers.level to level+1
var args = [i+1, 0];
args.push.apply(args, newMembers);
members.splice.apply(members, args);
expanded += newMembers.length;
i += newMembers.length + this.expandMembers(members, toggles[member.name], i+1, level+1, context);
}
}
return expanded;
},
showMembers: function(members, update, scrollTop)
{
// If we are still in the midst of inserting rows, cancel all pending
// insertions here - this is a big speedup when stepping in the debugger
if (this.timeouts)
{
for (var i = 0; i < this.timeouts.length; ++i)
this.context.clearTimeout(this.timeouts[i]);
delete this.timeouts;
}
if (!members.length)
return this.showEmptyMembers();
var panelNode = this.panelNode;
var priorScrollTop = scrollTop == undefined ? panelNode.scrollTop : scrollTop;
// If we are asked to "update" the current view, then build the new table
// offscreen and swap it in when it's done
var offscreen = update && panelNode.firstChild;
var dest = offscreen ? this.document : panelNode;
var table = this.tag.replace({domPanel: this, toggles: this.toggles}, dest);
var tbody = table.lastChild;
var rowTag = DirTablePlate.rowTag;
// Insert the first slice immediately
var setSize = members.length;
var slice = members.splice(0, insertSliceSize);
var result = rowTag.insertRows({members: slice}, tbody.lastChild);
var rowCount = 1;
var panel = this;
dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]);
var timeouts = [];
var delay = 0;
while (members.length)
{
timeouts.push(this.context.setTimeout(function(slice)
{
result = rowTag.insertRows({members: slice}, tbody.lastChild);
rowCount += insertSliceSize;
dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]);
if ((panelNode.scrollHeight+panelNode.offsetHeight) >= priorScrollTop)
panelNode.scrollTop = priorScrollTop;
}, delay, members.splice(0, insertSliceSize)));
delay += insertInterval;
}
if (offscreen)
{
timeouts.push(this.context.setTimeout(function()
{
if (panelNode.firstChild)
panelNode.replaceChild(table, panelNode.firstChild);
else
panelNode.appendChild(table);
// Scroll back to where we were before
panelNode.scrollTop = priorScrollTop;
}, delay));
}
else
{
timeouts.push(this.context.setTimeout(function()
{
panelNode.scrollTop = scrollTop == undefined ? 0 : scrollTop;
}, delay));
}
this.timeouts = timeouts;
},
showEmptyMembers: function()
{
FirebugReps.Warning.tag.replace({object: "NoMembersWarning"}, this.panelNode);
},
findPathObject: function(object)
{
var pathIndex = -1;
for (var i = 0; i < this.objectPath.length; ++i)
{
if (this.getPathObject(i) == object)
return i;
}
return -1;
},
getPathObject: function(index)
{
var object = this.objectPath[index];
if (object instanceof Property)
return object.getObject();
else
return object;
},
getRowObject: function(row)
{
var object = getRowOwnerObject(row);
return object ? object : this.selection;
},
getRealRowObject: function(row)
{
var object = this.getRowObject(row);
return this.getRealObject(object);
},
getRowPropertyValue: function(row)
{
var object = this.getRealRowObject(row);
return this.getObjectPropertyValue(object, row.domObject.name);
},
getObjectPropertyValue: function(object, propName)
{
if (!object)
return;
// Get the value with try-catch statement. This method is used also wihin
// getContextMenuItems where the exception would break the context menu.
// 1) The Firebug.Debugger.evaluate can throw
// 2) object[propName] can also throws in case of e.g. non existing "abc.abc" prop name.
try
{
if (object instanceof jsdIStackFrame)
return Firebug.Debugger.evaluate(propName, this.context);
else
return object[propName];
}
catch (err)
{
}
},
getRowPathName: function(row)
{
var name = row.domObject.name;
var seperator = "";
if(name.match(/^[\d]+$/))//ordinal
return ["", "["+name+"]"];
else if(name.match(rxIdentifier))//identifier
return [".", name];
else//map keys
return ["", "[\""+name.replace(/\\/g, "\\\\").replace(/"/g,"\\\"") + "\"]"];
},
copyName: function(row)
{
var value = this.getRowPathName(row);
value = value[1];//don't want the seperator
copyToClipboard(value);
},
copyPath: function(row)
{
var path = this.getPropertyPath(row);
copyToClipboard(path.join(""));
},
/*
* Walk from the current row up to the most ancient parent, building an array.
* @return array of property names and separators, eg ['foo','.','bar'].
*/
getPropertyPath: function(row)
{
var path = [];
for(var current = row; current ; current = getParentRow(current))
path = this.getRowPathName(current).concat(path);
path.splice(0,1); //don't want the first seperator
return path;
},
copyProperty: function(row)
{
var value = this.getRowPropertyValue(row);
copyToClipboard(value);
},
editProperty: function(row, editValue)
{
if (hasClass(row, "watchNewRow"))
{
if (this.context.stopped)
Firebug.Editor.startEditing(row, "");
else if (Firebug.Console.isAlwaysEnabled()) // not stopped in debugger, need command line
{
if (Firebug.CommandLine.onCommandLineFocus())
Firebug.Editor.startEditing(row, "");
else
row.innerHTML = $STR("warning.Command line blocked?");
}
else
row.innerHTML = $STR("warning.Console must be enabled");
}
else if (hasClass(row, "watchRow"))
{
Firebug.Editor.startEditing(row, getRowName(row));
}
else
{
var object = this.getRowObject(row);
this.context.thisValue = object;
if (!editValue)
{
var propValue = this.getRowPropertyValue(row);
var type = typeof(propValue);
if (type == "undefined" || type == "number" || type == "boolean")
editValue = propValue;
else if (type == "string")
editValue = "\"" + escapeJS(propValue) + "\"";
else if (propValue == null)
editValue = "null";
else if (object instanceof Window || object instanceof jsdIStackFrame)
editValue = getRowName(row);
else
editValue = "this." + getRowName(row);
}
Firebug.Editor.startEditing(row, editValue);
}
},
deleteProperty: function(row)
{
if (hasClass(row, "watchRow"))
this.deleteWatch(row);
else
{
var object = getRowOwnerObject(row);
if (!object)
object = this.selection;
object = this.getRealObject(object);
if (object)
{
var name = getRowName(row);
try
{
delete object[name];
}
catch (exc)
{
return;
}
this.rebuild(true);
this.markChange();
}
}
},
setPropertyValue: function(row, value) // value must be string
{
var name = getRowName(row);
if (name == "this")
return;
var object = this.getRealRowObject(row);
if (object && !(object instanceof jsdIStackFrame))
{
// unwrappedJSObject.property = unwrappedJSObject
Firebug.CommandLine.evaluate(value, this.context, object, this.context.getGlobalScope(),
function success(result, context)
{
object[name] = result;
},
function failed(exc, context)
{
try
{
object[name] = String(value); // unwrappedJSobject.property = string
}
catch (exc)
{
return;
}
}
);
}
else if (this.context.stopped)
{
try
{
Firebug.CommandLine.evaluate(name+"="+value, this.context);
}
catch (exc)
{
try
{
// See catch block above...
object[name] = String(value); // unwrappedJSobject.property = string
}
catch (exc)
{
return;
}
}
}
this.rebuild(true);
this.markChange();
},
highlightRow: function(row)
{
if (this.highlightedRow)
cancelClassTimed(this.highlightedRow, "jumpHighlight", this.context);
this.highlightedRow = row;
if (row)
setClassTimed(row, "jumpHighlight", this.context);
},
breakOnProperty: function(row)
{
var member = row.domObject;
if (!member)
return;
// Bail out if this property is not breakable.
if (!member.breakable)
return;
//xxxHonza: don't use getRowName to get the prop name. From some reason
// unwatch doesn't work if row.firstChild.textContent is used.
// It works only from within the watch handler method if the passed param
// name is used.
var name = member.name;
if (name == "this")
return;
var object = this.getRowObject(row);
object = this.getRealObject(object);
if (!object)
return;
// Create new or remove an existing breakpoint.
var breakpoints = this.context.dom.breakpoints;
var bp = breakpoints.findBreakpoint(object, name);
if (bp)
{
row.removeAttribute("breakpoint");
breakpoints.removeBreakpoint(object, name);
}
else
{
breakpoints.addBreakpoint(object, name, this, row);
row.setAttribute("breakpoint", "true");
}
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// extends Panel
initialize: function()
{
this.objectPath = [];
this.propertyPath = [];
this.viewPath = [];
this.pathIndex = -1;
this.toggles = {};
Firebug.Panel.initialize.apply(this, arguments);
},
destroy: function(state)
{
var view = this.viewPath[this.pathIndex];
if (view && this.panelNode.scrollTop)
view.scrollTop = this.panelNode.scrollTop;
if (this.pathIndex)
state.pathIndex = this.pathIndex;
if (this.viewPath)
state.viewPath = this.viewPath;
if (this.propertyPath)
state.propertyPath = this.propertyPath;
if (this.propertyPath.length > 0 && !this.propertyPath[1])
state.firstSelection = persistObject(this.getPathObject(1), this.context);
Firebug.Panel.destroy.apply(this, arguments);
},
show: function(state)
{
if (!this.selection)
{
if (!state)
{
this.select(null);
return;
}
if (state.viewPath)
this.viewPath = state.viewPath;
if (state.propertyPath)
this.propertyPath = state.propertyPath;
var selectObject = defaultObject = this.getDefaultSelection(this.context);
if (state.firstSelection)
{
var restored = state.firstSelection(this.context);
if (restored)
{
selectObject = restored;
this.objectPath = [defaultObject, restored];
}
else
this.objectPath = [defaultObject];
}
else
this.objectPath = [defaultObject];
if (this.propertyPath.length > 1)
selectObject = this.resetPaths(selectObject);
var selection = state.pathIndex <= this.objectPath.length-1
? this.getPathObject(state.pathIndex)
: this.getPathObject(this.objectPath.length-1);
this.select(selection);
}
},
resetPaths: function(selectObject)
{
for (var i = 1; i < this.propertyPath.length; ++i)
{
var name = this.propertyPath[i];
if (!name)
continue;
var object = selectObject;
try
{
selectObject = object[name];
}
catch (exc)
{
selectObject = null;
}
if (selectObject)
{
this.objectPath.push(new Property(object, name));
}
else
{
// If we can't access a property, just stop
this.viewPath.splice(i);
this.propertyPath.splice(i);
this.objectPath.splice(i);
selectObject = this.getPathObject(this.objectPath.length-1);
break;
}
}
},
hide: function()
{
var view = this.viewPath[this.pathIndex];
if (view && this.panelNode.scrollTop)
view.scrollTop = this.panelNode.scrollTop;
},
getBreakOnNextTooltip: function(enabled)
{
return (enabled ? $STR("dom.Disable Break On Property Change") :
$STR("dom.Break On Property Change"));
},
supportsObject: function(object)
{
if (object == null)
return 1000;
if (typeof(object) == "undefined")
return 1000;
else if (object instanceof SourceLink)
return 0;
else
return 1; // just agree to support everything but not aggressively.
},
refresh: function()
{
this.rebuild(true);
},
updateSelection: function(object)
{
var previousIndex = this.pathIndex;
var previousView = previousIndex == -1 ? null : this.viewPath[previousIndex];
var newPath = this.pathToAppend;
delete this.pathToAppend;
var pathIndex = this.findPathObject(object);
if (newPath || pathIndex == -1)
{
this.toggles = {};
if (newPath)
{
// Remove everything after the point where we are inserting, so we
// essentially replace it with the new path
if (previousView)
{
if (this.panelNode.scrollTop)
previousView.scrollTop = this.panelNode.scrollTop;
this.objectPath.splice(previousIndex+1);
this.propertyPath.splice(previousIndex+1);
this.viewPath.splice(previousIndex+1);
}
var value = this.getPathObject(previousIndex);
if (!value)
{
return;
}
for (var i = 0; i < newPath.length; ++i)
{
var name = newPath[i];
var object = value;
try
{
value = value[name];
}
catch(exc)
{
return;
}
++this.pathIndex;
this.objectPath.push(new Property(object, name));
this.propertyPath.push(name);
this.viewPath.push({toggles: this.toggles, scrollTop: 0});
}
}
else
{
this.toggles = {};
var win = this.context.getGlobalScope();
if (object == win)
{
this.pathIndex = 0;
this.objectPath = [win];
this.propertyPath = [null];
this.viewPath = [{toggles: this.toggles, scrollTop: 0}];
}
else
{
this.pathIndex = 1;
this.objectPath = [win, object];
this.propertyPath = [null, null];
this.viewPath = [
{toggles: {}, scrollTop: 0},
{toggles: this.toggles, scrollTop: 0}
];
}
}
this.panelNode.scrollTop = 0;
this.rebuild();
}
else
{
this.pathIndex = pathIndex;
var view = this.viewPath[pathIndex];
this.toggles = view ? view.toggles : {};
// Persist the current scroll location
if (previousView && this.panelNode.scrollTop)
previousView.scrollTop = this.panelNode.scrollTop;
this.rebuild(false, view ? view.scrollTop : 0);
}
},
getObjectPath: function(object)
{
return this.objectPath;
},
getDefaultSelection: function()
{
return this.context.getGlobalScope();
},
updateOption: function(name, value)
{
const optionMap = {showUserProps: 1, showUserFuncs: 1, showDOMProps: 1,
showDOMFuncs: 1, showDOMConstants: 1};
if ( optionMap.hasOwnProperty(name) )
this.rebuild(true);
},
getOptionsMenuItems: function()
{
return [
optionMenu("ShowUserProps", "showUserProps"),
optionMenu("ShowUserFuncs", "showUserFuncs"),
optionMenu("ShowDOMProps", "showDOMProps"),
optionMenu("ShowDOMFuncs", "showDOMFuncs"),
optionMenu("ShowDOMConstants", "showDOMConstants"),
"-",
{label: "Refresh", command: bindFixed(this.rebuild, this, true) }
];
},
getContextMenuItems: function(object, target)
{
var row = getAncestorByClass(target, "memberRow");
var items = [];
if (row)
{
var rowName = getRowName(row);
var rowObject = this.getRowObject(row);
var rowValue = this.getRowPropertyValue(row);
var isWatch = hasClass(row, "watchRow");
var isStackFrame = rowObject instanceof jsdIStackFrame;
items.push(
"-",
{label: "Copy Name",
command: bindFixed(this.copyName, this, row) },
{label: "Copy Path",
command: bindFixed(this.copyPath, this, row) }
);
if (typeof(rowValue) == "string" || typeof(rowValue) == "number")
{
// Functions already have a copy item in their context menu
items.push(
{label: "CopyValue",
command: bindFixed(this.copyProperty, this, row) }
);
}
items.push(
"-",
{label: isWatch ? "EditWatch" : (isStackFrame ? "EditVariable" : "EditProperty"),
command: bindFixed(this.editProperty, this, row) }
);
if (isWatch || (!isStackFrame && !isDOMMember(rowObject, rowName)))
{
items.push(
{label: isWatch ? "DeleteWatch" : "DeleteProperty",
command: bindFixed(this.deleteProperty, this, row) }
);
}
var member = row ? row.domObject : null;
if (!isDOMMember(rowObject, rowName) && member && member.breakable)
{
items.push(
"-",
{label: "html.dom.label.Break On Property Change", type: "checkbox",
checked: this.context.dom.breakpoints.findBreakpoint(rowObject, rowName),
command: bindFixed(this.breakOnProperty, this, row)}
);
}
}
items.push(
"-",
{label: "Refresh", command: bindFixed(this.rebuild, this, true) }
);
return items;
},
getEditor: function(target, value)
{
if (!this.editor)
this.editor = new DOMEditor(this.document);
return this.editor;
}
});
// ************************************************************************************************
var DOMMainPanel = Firebug.DOMPanel = function () {};
Firebug.DOMPanel.DirTable = DirTablePlate;
DOMMainPanel.prototype = extend(Firebug.DOMBasePanel.prototype,
{
selectRow: function(row, target)
{
if (!target)
target = row.lastChild.firstChild;
if (!target || !target.repObject)
return;
this.pathToAppend = getPath(row);
// If the object is inside an array, look up its index
var valueBox = row.lastChild.firstChild;
if (hasClass(valueBox, "objectBox-array"))
{
var arrayIndex = FirebugReps.Arr.getItemIndex(target);
this.pathToAppend.push(arrayIndex);
}
// Make sure we get a fresh status path for the object, since otherwise
// it might find the object in the existing path and not refresh it
Firebug.chrome.clearStatusPath();
this.select(target.repObject, true);
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
onClick: function(event)
{
var repNode = Firebug.getRepNode(event.target);
if (repNode)
{
var row = getAncestorByClass(event.target, "memberRow");
if (row)
{
this.selectRow(row, repNode);
cancelEvent(event);
}
}
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// extends Panel
name: "dom",
searchable: true,
statusSeparator: ">",
initialize: function()
{
this.onClick = bind(this.onClick, this);
Firebug.DOMBasePanel.prototype.initialize.apply(this, arguments);
},
initializeNode: function(oldPanelNode)
{
this.panelNode.addEventListener("click", this.onClick, false);
dispatch([Firebug.A11yModel], 'onInitializeNode', [this, 'console']);
},
destroyNode: function()
{
this.panelNode.removeEventListener("click", this.onClick, false);
dispatch([Firebug.A11yModel], 'onDestroyNode', [this, 'console']);
},
search: function(text, reverse)
{
if (!text)
{
delete this.currentSearch;
this.highlightRow(null);
return false;
}
var row;
if (this.currentSearch && text == this.currentSearch.text)
row = this.currentSearch.findNext(true, undefined, reverse, Firebug.Search.isCaseSensitive(text));
else
{
function findRow(node) { return getAncestorByClass(node, "memberRow"); }
this.currentSearch = new TextSearch(this.panelNode, findRow);
row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text));
}
if (row)
{
var sel = this.document.defaultView.getSelection();
sel.removeAllRanges();
sel.addRange(this.currentSearch.range);
scrollIntoCenterView(row, this.panelNode);
this.highlightRow(row);
dispatch([Firebug.A11yModel], 'onDomSearchMatchFound', [this, text, row]);
return true;
}
else
{
dispatch([Firebug.A11yModel], 'onDomSearchMatchFound', [this, text, null]);
return false;
}
}
});
// ************************************************************************************************
function DOMSidePanel() {}
DOMSidePanel.prototype = extend(Firebug.DOMBasePanel.prototype,
{
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// extends Panel
name: "domSide",
parentPanel: "html",
order: 3,
initializeNode: function(oldPanelNode)
{
dispatch([Firebug.A11yModel], 'onInitializeNode', [this, 'console']);
},
destroyNode: function()
{
dispatch([Firebug.A11yModel], 'onDestroyNode', [this, 'console']);
},
});
// ************************************************************************************************
function WatchPanel() {}
WatchPanel.prototype = extend(Firebug.DOMBasePanel.prototype,
{
tag: DirTablePlate.watchTag,
rebuild: function()
{
this.updateSelection(this.selection);
},
showEmptyMembers: function()
{
this.tag.replace({domPanel: this, toggles: {}}, this.panelNode);
},
addWatch: function(expression)
{
if (!this.watches)
this.watches = [];
this.watches.splice(0, 0, expression);
this.rebuild(true);
},
removeWatch: function(expression)
{
if (!this.watches)
return;
var index = this.watches.indexOf(expression);
if (index != -1)
this.watches.splice(index, 1);
},
editNewWatch: function(value)
{
var watchNewRow = this.panelNode.getElementsByClassName("watchNewRow").item(0);
if (watchNewRow)
this.editProperty(watchNewRow, value);
},
setWatchValue: function(row, value)
{
var rowIndex = getWatchRowIndex(row);
this.watches[rowIndex] = value;
this.rebuild(true);
},
deleteWatch: function(row)
{
var rowIndex = getWatchRowIndex(row);
this.watches.splice(rowIndex, 1);
this.rebuild(true);
this.context.setTimeout(bindFixed(function()
{
this.showToolbox(null);
}, this));
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
showToolbox: function(row)
{
var toolbox = this.getToolbox();
if (row)
{
if (hasClass(row, "editing"))
return;
toolbox.watchRow = row;
var offset = getClientOffset(row);
toolbox.style.top = offset.y + "px";
this.panelNode.appendChild(toolbox);
}
else
{
delete toolbox.watchRow;
if (toolbox.parentNode)
toolbox.parentNode.removeChild(toolbox);
}
},
getToolbox: function()
{
if (!this.toolbox)
this.toolbox = ToolboxPlate.tag.replace({domPanel: this}, this.document);
return this.toolbox;
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
onMouseDown: function(event)
{
var watchNewRow = getAncestorByClass(event.target, "watchNewRow");
if (watchNewRow)
{
this.editProperty(watchNewRow);
cancelEvent(event);
}
},
onMouseOver: function(event)
{
var watchRow = getAncestorByClass(event.target, "watchRow");
if (watchRow)
this.showToolbox(watchRow);
},
onMouseOut: function(event)
{
if (isAncestor(event.relatedTarget, this.getToolbox()))
return;
var watchRow = getAncestorByClass(event.relatedTarget, "watchRow");
if (!watchRow)
this.showToolbox(null);
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// extends Panel
name: "watches",
order: 0,
parentPanel: "script",
initialize: function()
{
this.onMouseDown = bind(this.onMouseDown, this);
this.onMouseOver = bind(this.onMouseOver, this);
this.onMouseOut = bind(this.onMouseOut, this);
Firebug.DOMBasePanel.prototype.initialize.apply(this, arguments);
},
destroy: function(state)
{
state.watches = this.watches;
Firebug.Panel.destroy.apply(this, arguments);
},
show: function(state)
{
if (state && state.watches)
this.watches = state.watches;
},
initializeNode: function(oldPanelNode)
{
this.panelNode.addEventListener("mousedown", this.onMouseDown, false);
this.panelNode.addEventListener("mouseover", this.onMouseOver, false);
this.panelNode.addEventListener("mouseout", this.onMouseOut, false);
dispatch([Firebug.A11yModel], "onInitializeNode", [this, 'console']);
},
destroyNode: function()
{
this.panelNode.removeEventListener("mousedown", this.onMouseDown, false);
this.panelNode.removeEventListener("mouseover", this.onMouseOver, false);
this.panelNode.removeEventListener("mouseout", this.onMouseOut, false);
dispatch([Firebug.A11yModel], "onDestroyNode", [this, 'console']);
},
refresh: function()
{
this.rebuild(true);
},
updateSelection: function(object)
{
dispatch([Firebug.A11yModel], 'onBeforeDomUpdateSelection', [this]);
var frame = this.context.currentFrame;
var newFrame = frame && frame.isValid && frame.script != this.lastScript;
if (newFrame)
{
this.toggles = {};
this.lastScript = frame.script;
}
var members = [];
if (this.watches)
{
for (var i = 0; i < this.watches.length; ++i)
{
var expr = this.watches[i];
var value = null;
Firebug.CommandLine.evaluate(expr, this.context, null, this.context.getGlobalScope(),
function success(result, context)
{
value = result;
},
function failed(result, context)
{
var exc = result;
value = new ErrorCopy(exc+"");
}
);
addMember(object, "watch", members, expr, value, 0);
}
}
if (frame && frame.isValid)
{
var thisVar = unwrapIValue(frame.thisValue);
addMember(object, "user", members, "this", thisVar, 0);
var scopeChain = this.generateScopeChain(frame.scope);
addMember(object, "scopes", members, "scopeChain", scopeChain, 0);
members.push.apply(members, this.getMembers(scopeChain[0], 0, this.context));
}
this.expandMembers(members, this.toggles, 0, 0, this.context);
this.showMembers(members, !newFrame);
},
generateScopeChain: function (scope)
{
var ret = [];
while (scope) {
var scopeVars;
// getWrappedValue will not contain any variables for closure
// scopes, so we want to special case this to get all variables
// in all cases.
if (scope.jsClassName == "Call") {
scopeVars = {};
var listValue = {value: null}, lengthValue = {value: 0};
scope.getProperties(listValue, lengthValue);
for (var i = 0; i < lengthValue.value; ++i)
{
var prop = listValue.value[i];
var name = unwrapIValue(prop.name);
if (ignoreVars[name] == 1)
{
continue;
}
scopeVars[name] = unwrapIValue(prop.value);
}
} else {
scopeVars = unwrapIValue(scope);
}
if (scopeVars && scopeVars.hasOwnProperty)
{
if (!scopeVars.hasOwnProperty("toString")) {
(function() {
var className = scope.jsClassName;
scopeVars.toString = function() {
return $STR(className + " Scope");
};
})();
}
ret.push(scopeVars);
}
else
{
}
scope = scope.jsParent;
}
ret.toString = function() {
return $STR("Scope Chain");
};
return ret;
},
});
// ************************************************************************************************
// Local Helpers
function DOMEditor(doc)
{
this.box = this.tag.replace({}, doc, this);
this.input = this.box;
this.tabNavigation = false;
this.tabCompletion = true;
this.completeAsYouType = false;
this.fixedWidth = true;
this.autoCompleter = Firebug.CommandLine.autoCompleter;
}
DOMEditor.prototype = domplate(Firebug.InlineEditor.prototype,
{
tag:
INPUT({"class": "fixedWidthEditor a11yFocusNoTab",
type: "text", title:$STR("NewWatch"),
oninput: "$onInput", onkeypress: "$onKeyPress"}),
endEditing: function(target, value, cancel)
{
// XXXjoe Kind of hackish - fix me
delete this.panel.context.thisValue;
if (cancel || value == "")
return;
var row = getAncestorByClass(target, "memberRow");
dispatch([Firebug.A11yModel], 'onWatchEndEditing', [this.panel]);
if (!row)
this.panel.addWatch(value);
else if (hasClass(row, "watchRow"))
this.panel.setWatchValue(row, value);
else
this.panel.setPropertyValue(row, value);
}
});
// ************************************************************************************************
// Local Helpers
function isClassFunction(fn)
{
try
{
for (var name in fn.prototype)
return true;
} catch (exc) {}
return false;
}
function isArguments(obj)
{
try
{
return isFinite(obj.length) && obj.length > 0 && typeof obj.callee === "function";
} catch (exc) {}
return false;
}
function addMember(object, type, props, name, value, level, order, context)
{
var rep = Firebug.getRep(value); // do this first in case a call to instanceof reveals contents
var tag = rep.shortTag ? rep.shortTag : rep.tag;
var valueType = typeof(value);
var hasChildren = hasProperties(value) && !(value instanceof ErrorCopy) &&
(valueType == "function" || (valueType == "object" && value != null)
|| (valueType == "string" && value.length > Firebug.stringCropLength));
// Special case for "arguments", which is not enumerable by for...in statement
// and so, hasProperties always returns false.
if (!hasChildren && value) // arguments will never be falsy if the arguments exist
hasChildren = isArguments(value);
var member = {
object: object,
name: name,
value: value,
type: type,
rowClass: "memberRow-"+type,
open: "",
order: order,
level: level,
indent: level*16,
hasChildren: hasChildren,
tag: tag
};
// The context doesn't have to be specified (e.g. in case of Watch panel that is based
// on the same template as the DOM panel, but doesn't show any breakpoints).
if (context)
{
// xxxHonza: Support for object change not implemented yet.
member.breakable = !hasChildren;
// xxxHonza: Disable breaking on direct window properties, see #520572
if (object instanceof Ci.nsIDOMWindow)
member.breakable = false;
var breakpoints = context.dom.breakpoints;
var bp = breakpoints.findBreakpoint(object, name);
if (bp)
{
member.breakpoint = true;
member.disabledBreakpoint = !bp.checked;
}
}
// If the property is implemented using a getter function (and there is no setter
// implemented) use a "get" prefix that is displayed in the UI.
var o = unwrapObject(object);
member.prefix = (o.__lookupGetter__(name) && !o.__lookupSetter__(name)) ? "get " : "";
props.push(member);
return member;
}
function getWatchRowIndex(row)
{
var index = -1;
for (; row && hasClass(row, "watchRow"); row = row.previousSibling)
++index;
return index;
}
function getRowName(row)
{
var labelNode = row.getElementsByClassName("memberLabelCell").item(0);
return labelNode.textContent;
}
function getRowValue(row)
{
var valueNode = row.getElementsByClassName("memberValueCell").item(0);
return valueNode.firstChild.repObject;
}
function getRowOwnerObject(row)
{
var parentRow = getParentRow(row);
if (parentRow)
return getRowValue(parentRow);
}
function getParentRow(row)
{
var level = parseInt(row.getAttribute("level"))-1;
for (row = row.previousSibling; row; row = row.previousSibling)
{
if (parseInt(row.getAttribute("level")) == level)
return row;
}
}
function getPath(row)
{
var name = getRowName(row);
var path = [name];
var level = parseInt(row.getAttribute("level"))-1;
for (row = row.previousSibling; row; row = row.previousSibling)
{
if (parseInt(row.getAttribute("level")) == level)
{
var name = getRowName(row);
path.splice(0, 0, name);
--level;
}
}
return path;
}
function findRow(parentNode, object)
{
var rows = parentNode.getElementsByClassName("memberRow");
for (var i=0; i<rows.length; i++)
{
var row = rows[i];
if (object == row.domObject.object)
return row;
}
return row;
}
// ************************************************************************************************
Firebug.DOMModule.DebuggerListener =
{
getBreakpoints: function(context, groups)
{
if (!context.dom.breakpoints.isEmpty())
groups.push(context.dom.breakpoints);
}
};
Firebug.DOMModule.BreakpointRep = domplate(Firebug.Rep,
{
inspectable: false,
tag:
DIV({"class": "breakpointRow focusRow", _repObject: "$bp",
role: "option", "aria-checked": "$bp.checked"},
DIV({"class": "breakpointBlockHead", onclick: "$onEnable"},
INPUT({"class": "breakpointCheckbox", type: "checkbox",
_checked: "$bp.checked", tabindex : "-1"}),
SPAN({"class": "breakpointName"}, "$bp.propName"),
IMG({"class": "closeButton", src: "blank.gif", onclick: "$onRemove"})
),
DIV({"class": "breakpointCode"},
TAG("$bp.object|getObjectTag", {object: "$bp.object"})
)
),
getObjectTag: function(object)
{
var rep = Firebug.getRep(object);
return rep.shortTag ? rep.shortTag : rep.tag;
},
onRemove: function(event)
{
cancelEvent(event);
if (!hasClass(event.target, "closeButton"))
return;
var bpPanel = Firebug.getElementPanel(event.target);
var context = bpPanel.context;
// Remove from list of breakpoints.
var row = getAncestorByClass(event.target, "breakpointRow");
var bp = row.repObject;
context.dom.breakpoints.removeBreakpoint(bp.object, bp.propName);
// Remove from the UI.
bpPanel.noRefresh = true;
bpPanel.removeRow(row);
bpPanel.noRefresh = false;
var domPanel = context.getPanel("dom", true);
if (domPanel)
{
var domRow = findRow(domPanel.panelNode, bp.object);
if (domRow)
{
domRow.removeAttribute("breakpoint");
domRow.removeAttribute("disabledBreakpoint");
}
}
},
onEnable: function(event)
{
var checkBox = event.target;
if (!hasClass(checkBox, "breakpointCheckbox"))
return;
var bpPanel = Firebug.getElementPanel(event.target);
var context = bpPanel.context;
var bp = getAncestorByClass(checkBox, "breakpointRow").repObject;
bp.checked = checkBox.checked;
var domPanel = context.getPanel("dom", true);
if (domPanel)
{
var row = findRow(domPanel.panelNode, bp.object);
if (row)
row.setAttribute("disabledBreakpoint", bp.checked ? "false" : "true");
}
},
supportsObject: function(object)
{
return object instanceof Breakpoint;
}
});
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
function Breakpoint(object, propName, objectPath, context)
{
this.context = context;
this.propName = propName;
this.objectPath = objectPath;
this.object = object;
this.checked = true;
}
Breakpoint.prototype =
{
watchProperty: function()
{
if (!this.object)
return;
try
{
var self = this;
this.object.watch(this.propName, function handler(prop, oldval, newval)
{
// XXXjjb Beware: in playing with this feature I hit too much recursion multiple times with console.log
// TODO Do something cute in the UI with the error bubble thing
if (self.checked)
{
self.context.breakingCause = {
title: $STR("dom.Break On Property"),
message: cropString(prop, 200),
prevValue: oldval,
newValue: newval
};
Firebug.Breakpoint.breakNow(self.context.getPanel("dom", true));
}
return newval;
});
}
catch (exc)
{
return false;
}
return true;
},
unwatchProperty: function()
{
if (!this.object)
return;
try
{
this.object.unwatch(this.propName);
}
catch (exc)
{
}
}
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
function DOMBreakpointGroup()
{
this.breakpoints = [];
}
DOMBreakpointGroup.prototype = extend(new Firebug.Breakpoint.BreakpointGroup(),
{
name: "domBreakpoints",
title: $STR("dom.label.DOM Breakpoints"),
addBreakpoint: function(object, propName, panel, row)
{
var path = panel.getPropertyPath(row);
path.pop();
// We don't want the last dot.
if (path.length > 0 && path[path.length-1] == ".")
path.pop();
var objectPath = path.join("");
var bp = new Breakpoint(object, propName, objectPath, panel.context);
if (bp.watchProperty());
this.breakpoints.push(bp);
},
removeBreakpoint: function(object, propName)
{
var bp = this.findBreakpoint(object, propName);
if (bp)
{
bp.unwatchProperty();
remove(this.breakpoints, bp);
}
},
matchBreakpoint: function(bp, args)
{
var object = args[0];
var propName = args[1];
return bp.object == object && bp.propName == propName;
},
// Persistence
load: function(context)
{
var panelState = getPersistedState(context, "dom");
if (panelState.breakpoints)
this.breakpoints = panelState.breakpoints;
this.enumerateBreakpoints(function(bp)
{
try
{
// xxxHonza: Firebug.CommandLine.evaluate should be reused if possible.
// xxxJJB: The Components.utils.evalInSandbox fails from some reason.
var expr = "context.window.wrappedJSObject." + bp.objectPath;
bp.object = eval(expr);
bp.watchProperty();
}
catch (err)
{
}
});
},
store: function(context)
{
this.enumerateBreakpoints(function(bp)
{
bp.object = null;
});
var panelState = getPersistedState(context, "dom");
panelState.breakpoints = this.breakpoints;
},
});
// ************************************************************************************************
Firebug.registerModule(Firebug.DOMModule);
Firebug.registerPanel(DOMMainPanel);
Firebug.registerPanel(DOMSidePanel);
Firebug.registerPanel(WatchPanel);
Firebug.registerRep(Firebug.DOMModule.BreakpointRep);
// ************************************************************************************************
}});